﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Controls.Primitives;
using System.Collections;
using System.Collections.ObjectModel;

namespace Pavilion.DesignerTool.ViewTools
{
    public static class ItemsOrganizer
    {
        #region IsReordableProperty

        public static readonly DependencyProperty IsReordableProperty = DependencyProperty.RegisterAttached("IsReordable", typeof(Boolean), typeof(ItemsOrganizer),
            new FrameworkPropertyMetadata(IsReordable_PropertyChanged));

        public static void SetIsReordable(DependencyObject element, Boolean value)
        {
            element.SetValue(IsReordableProperty, value);
        }

        public static Boolean GetIsReordable(DependencyObject element)
        {
            return (Boolean)element.GetValue(IsReordableProperty);
        }

        #endregion

        #region DraggingDirectionProperty

        public static readonly DependencyProperty DraggingDirectionProperty = DependencyProperty.RegisterAttached("DraggingDirection", typeof(DraggingDirection), typeof(ItemsOrganizer),
            new PropertyMetadata(DraggingDirection.Both));

        public static void SetDraggingDirection(DependencyObject element, DraggingDirection value)
        {
            element.SetValue(DraggingDirectionProperty, value);
        }

        public static DraggingDirection GetDraggingDirection(DependencyObject element)
        {
            return (DraggingDirection)element.GetValue(DraggingDirectionProperty);
        }

        #endregion
        
        private static Selector currentSelector;
        private static AdornerLayer AdornerLayer { get { return AdornerLayer.GetAdornerLayer((Visual)currentSelector); } }
        private static ItemPreviewAdorner overlayElement;
        private static int originalItemIndex;
        private static bool isDragging;

        private static void StartDragging(Selector selector)
        {
            isDragging = true;
            currentSelector = selector;
            originalItemIndex = currentSelector.SelectedIndex;

            ListBoxItem listBoxItem = (ListBoxItem)currentSelector.ItemContainerGenerator.ContainerFromIndex(currentSelector.SelectedIndex);
            overlayElement = new ItemPreviewAdorner((UIElement)currentSelector, listBoxItem);

            AdornerLayer.Add(overlayElement);
        }

        private static void UpdateAdornerDraggingPosition(DraggingDirection itemDragging)
        {
            if (isDragging && overlayElement != null)
            {
                Point currentPosition = (Point)Mouse.GetPosition((IInputElement)currentSelector);
                ListBoxItem listBoxItem = (ListBoxItem)currentSelector.ItemContainerGenerator.ContainerFromIndex(currentSelector.SelectedIndex);

                if (itemDragging == DraggingDirection.Horizontal)
                {
                    overlayElement.PositionY = listBoxItem.TranslatePoint(new Point(), currentSelector).Y;
                    overlayElement.PositionX = currentPosition.X;
                }

                if (itemDragging == DraggingDirection.Vertical)
                {
                    overlayElement.PositionX = listBoxItem.TranslatePoint(new Point(), currentSelector).X;
                    overlayElement.PositionY = currentPosition.Y;
                }
                if (itemDragging == DraggingDirection.Both)
                {
                    overlayElement.PositionX = currentPosition.X;
                    overlayElement.PositionY = currentPosition.Y;
                }
            }
        }

        private static void FinishDragging()
        {
            isDragging = false;
            AdornerLayer.Remove(overlayElement);

            InsertItemAtSelectedIndex(originalItemIndex);
            currentSelector = null;
        }

        private static void InsertItemAtSelectedIndex(int originalItemIndex)
        {
            if (originalItemIndex == currentSelector.SelectedIndex)
                return;

            if (!(currentSelector.ItemsSource is IMovableCollection))
                throw new NotSupportedException("The ItemsOrganizer only supports 'IMovableCollection' types. Please use startPosition collection that implements 'IMovableCollection'");

            IMovableCollection collection = (IMovableCollection)currentSelector.ItemsSource;
            object originalItem = currentSelector.Items[originalItemIndex];

            collection.Move(originalItemIndex, currentSelector.SelectedIndex);
            currentSelector.SelectedItem = originalItem;
        }
        
        public static void IsReordable_PropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            if ((bool)args.NewValue == true)
            {
                Selector itemsSelector = (Selector)obj;
                itemsSelector.PreviewMouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler(ItemsControl_PreviewMouseLeftButtonUp);
                itemsSelector.PreviewMouseMove += new System.Windows.Input.MouseEventHandler(ItemsControl_PreviewMouseMove);
            }
        }

        private static void ItemsControl_PreviewMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (isDragging)
                FinishDragging();
        }

        private static void ItemsControl_PreviewMouseMove(object sender, System.Windows.Input.MouseEventArgs e)
        {
            Selector newSelector = (Selector)sender;

            if (e.LeftButton == MouseButtonState.Pressed && newSelector.SelectedIndex >= 0)
            {
                if (!isDragging)
                    StartDragging(newSelector);

                UpdateAdornerDraggingPosition(GetDraggingDirection(currentSelector));
            }
        }

        public enum DraggingDirection
        {
            Horizontal,
            Vertical,
            Both
        }        
    }

    public class ItemPreviewAdorner : Adorner
    {
        private Rectangle child;
        private double positionX, positionY;

        public ItemPreviewAdorner(UIElement adornedElement, UIElement adorningElement)
            : base(adornedElement)
        {
            VisualBrush brush = new VisualBrush(adorningElement);
            brush.Opacity = 0.7;

            this.child = new Rectangle();
            this.child.Width = adorningElement.RenderSize.Width;
            this.child.Height = adorningElement.RenderSize.Height;
            this.child.Fill = brush;
            this.child.IsHitTestVisible = false;
        }

        public double PositionX
        {
            get { return this.positionX; }
            set { this.positionX = value; this.UpdatePosition(); }
        }

        public double PositionY
        {
            get { return this.positionY; }
            set { this.positionY = value; this.UpdatePosition(); }
        }

        protected override int VisualChildrenCount { get { return 1; } }

        public override GeneralTransform GetDesiredTransform(GeneralTransform transform)
        {
            GeneralTransformGroup result = new GeneralTransformGroup();
            result.Children.Add(base.GetDesiredTransform(transform));
            result.Children.Add(new TranslateTransform(this.PositionX, this.PositionY));
            return result;
        }

        protected override System.Windows.Size MeasureOverride(System.Windows.Size constraint)
        {
            this.child.Measure(constraint);
            return this.child.DesiredSize;
        }

        protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize)
        {
            this.child.Arrange(new Rect(finalSize));
            return finalSize;
        }

        protected override System.Windows.Media.Visual GetVisualChild(int index)
        {
            return this.child;
        }

        private void UpdatePosition()
        {
            AdornerLayer adornerLayer = this.Parent as AdornerLayer;

            if (adornerLayer != null)
                adornerLayer.Update(AdornedElement);
        }
    }
}

